/**
* @file mypoc.c
* Betriebssysteme Beispiel 2 - mypoc
*
* @author Christian Lachner <ic11b010@technikum-wien.at>
* @author Max Paflik <ic11b006@technikum-wien.at>
* @date 2012/04/21
*
* @version $Revision: 019 $
*
* URL: $HeadURL$
*
* Last Modified: $Author: Chris $
*/

#include "mypoc.h"

/* global process id */
static pid_t pid;
/* global file-pointer */
static FILE * gfp = NULL;


/**
*
* \brief Semi-POSIX.1-2001-compatible reimplementation of popen().
*
* The mypopen() function opens a process by creating a pipe, forking, and
* invoking the shell. Since a pipe is by definition unidirectional, the
* type argument may specify only reading or writing, not both; the
* resulting stream is correspondingly read-only or write-only.
* 
* The 'e' value for type is not implemented.
*
* \param command The commandline that mypopen runs to the invoked shell.
* \param type "r" for read-mode or "w" for write-mode.
*
* \return A filepointer to the pipe
* \retval NULL	on error
*
*/
FILE * mypopen(const char * command, const char * type) {
	int fd[2];

	/* multiple calls of mypopen without pclose */
	if (gfp != NULL) {
		errno = EAGAIN;
		return NULL;
	}

	/* - length of type must be 1
	 * - length of command must be greater 0
	 * - type must be 'r' or 'w' */
	if(strlen(type) != 1 || strlen(command) <= 0 || (type[0] != 'r' && type[0] != 'w')) {
		/* invalid type or command given */
		errno = EINVAL;
		return NULL;
	}

	if(pipe(fd) == -1) {
		/* error while pipe init */
		return NULL;
	}

	if((pid = fork()) == -1) {
		close(fd[1]);
		close(fd[0]);
		return NULL;
	}

	if(type[0] == 'r') {
		/* READ MODE */
		if(pid == 0) {
			/* i am the child */

			/* not needed */
			close(fd[0]);

			/* route stdout to writing end of pipe */
			if(dup2(fd[1], 1) == -1) {
				/* error doing pipe switchage */
				close(fd[1]); /* no error handling since nothing was able to write here yet */
				exit(EXIT_FAILURE);
			}

			/* close both pipe-ends
			 * no errorchecks since no errors can happen */
			close(fd[1]);

			/* excecure a shell with the appended command */
			execlp("/bin/sh", "sh", "-c", command, NULL);

			exit(EXIT_FAILURE);
		} else {
			/* i am the parent */

			/* close of write-end */
			close(fd[1]); /* no error handling since nothing was able to write here yet */

			/* open the fp for the reading end in r-mode and store it globally */
			if((gfp = fdopen(fd[0], "r")) == NULL) {
				close(fd[0]);

				return NULL;
			}
		}
	} else {
		/* WRITE MODE */
		if(pid == 0) {
			/* i am the child */

			/* not needed */
			close(fd[1]); /* no error handling since nothing was able to write here yet */

			/* route */
			if(dup2(fd[0], 0) == -1) {
				/* error doing pipe switchage */
				close(fd[0]);
				exit(EXIT_FAILURE);
			}

			/* close both pipe-ends */
			close(fd[0]);

			/* excecure a shell with the appended command */
			execlp("/bin/sh", "sh", "-c", command, NULL);

			exit(EXIT_FAILURE);
		} else {
			/* i am the parent */

			close(fd[0]);

			/* open the fp for the writing end in w-mode and store it globally */
			if((gfp = fdopen(fd[1], "w")) == NULL) {
				close(fd[1]); /* no error handling since nothing was able to write here yet */

				return NULL;
			}
		}
	}

	return gfp;
}


/**
*
* \brief Semi-POSIX.1-2001-compatible reimplementation of pclose().
*
* The mypclose() function waits for the associated process to terminate
* and returns the exit status of the command as returned by waitpid(2).
*
* \param fp The file-pointer that is being closed.
*
* \return The status-code as int.
* \retval -1 on error
*
*/
int mypclose(FILE * fp) {
	pid_t wpid;
	int status = 0;

	/* fp must not be NULL since we work with that thing */
	if(fp == NULL) {
		errno = EINVAL;
		return -1;
	}

	/* the global fp MUST not be NULL since, again, we work with that thing */
	if(gfp == NULL) {
		errno = ECHILD;
		return -1;
	}

	/* fp and gfp MUST be equal - otherwise we're working with the wrong pipe */
	if(fp != gfp) {
		errno = EINVAL;
		return -1;
	}

	/* close the fp
	 * fclose() takes care of flushing too */
	if(fclose(fp) != 0) {
		return -1;
	}

	/* fp is now closed, gfp is therefore invalid and must be set to NULL */
	gfp = NULL;

	/* wait for the child process to die */
	errno = 0;
	while((wpid = waitpid(pid, &status, 0)) != pid) {
		if(wpid == -1) {
			/* proccess interruption detected, continue waiting */
			if(errno == EINTR) {
				continue;
			}
			/* error */
			return -1;
		}
	}

	/* check if the child exited normally and return the according status */	
	if(WIFEXITED(status)) {
		return WEXITSTATUS(status);
	} else {
		errno = ECHILD;
		return -1;
	}
}
